﻿// FogBugz Community Case List Plugin
// ----------------------------------
// 
// Copyright 2010, 2011 by Sirius Technologies AG / Arsène von Wyss - avw@gmx.ch
// 
// Development has been sponsered by Sirius Technologies AG, Basel
// 
// Source:
// 
// https://communitycaselist.googlecode.com/hg/
// 
// License:
// 
// The library is distributed under the GNU Lesser General Public License:
// http://www.gnu.org/licenses/lgpl.html
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

using FogCreek.FogBugz;
using FogCreek.FogBugz.Database;
using FogCreek.FogBugz.Database.Entity;
using FogCreek.FogBugz.Globalization;
using FogCreek.FogBugz.Plugins.Entity;
using FogCreek.FogBugz.UI;
using FogCreek.FogBugz.UI.EditableTable;

using Sirius.FogBugz;

namespace Sirius.CommunityCaseList {
	partial class CommunityCaseList {
		private static readonly string[] statusInfoStrings = new[] {"Open", "Resolved", "Closed"};

		private static string GetHtmlStringOrEmpty(object obj) {
			if (obj is string) {
				return HttpUtility.HtmlEncode(obj.ToString());
			}
			return string.Empty;
		}

		private void FlushTable(StringBuilder result, CEditableTable table) {
			if (table.Body.Rows.Count > 0) {
				result.Append("<p>");
				result.Append(table.RenderHtml());
				result.Append("</p>");
				table.Body.Rows.Clear();
			}
		}

		private IEnumerable<CBug> GetCommunityBugs(int ixProject) {
			string currentPersonEmail = api.Person.GetCurrentPerson().sEmail ?? string.Empty;
			if (showCasesForAnyCustomerEmail || currentPersonEmail.Contains("@")) {
				Regex filter = new Regex(@"\b"+Regex.Escape(currentPersonEmail)+@"\b", RegexOptions.CultureInvariant|RegexOptions.ExplicitCapture|RegexOptions.IgnoreCase);
				CBugQuery bugQuery = api.Bug.NewBugQuery();
				bugQuery.IgnorePermissions = true;
				bugQuery.Limit = 0;
				bugQuery.ExcludeUnreadable = false;
				bugQuery.ExcludeUnwritable = false;
				bugQuery.AddInnerJoinSelect(GetUserPublicProjects(), "UserProject", "UserProject.ixProject=Bug.ixProject");
				bugQuery.AddInnerJoin("Category", "Bug.ixCategory=Category.ixCategory");
				bugQuery.AddWhere("(Bug.sTicket IS NOT NULL) AND (Bug.sTicket<>'')");
				bugQuery.AddWhere("(Bug.sCustomerEmail IS NOT NULL) AND (Bug.sCustomerEmail<>'')");
				bugQuery.AddWhere("FixFor.fDeleted=0");
				bugQuery.AddWhere("UserProject.ixProject=@ixProject");
				bugQuery.SetParamInt("ixProject", ixProject);
				bugQuery.AddOrderBy("Project.sProject, FixFor.dt DESC, FixFor.sFixFor, Priority.ixPriority, Bug.ixBug");
				bugQuery.AddSelect("Category.sCategory");
				bugQuery.AddSelect("Category.nIconType");
				foreach (CBug bug in bugQuery.List()) {
					bug.IgnorePermissions = true;
					string sCustomerEmail = bug.sCustomerEmail;
					string sTicket = bug.sTicket;
					bug.IgnorePermissions = false;
					if (IsValidTicket(sTicket) && (showCasesForAnyCustomerEmail || filter.IsMatch(sCustomerEmail))) {
						yield return bug;
					}
				}
			}
		}

		private CSelectQuery GetProjectAccess() {
			CSelectQuery aclQuery = api.Database.NewSelectQuery("ACL");
			aclQuery.Limit = 0;
			aclQuery.IgnorePermissions = true;
			aclQuery.AddSelect("ACL.ixProject");
			aclQuery.AddSelect("MAX(ACL.iPermission) AS iPermission");
			aclQuery.AddLeftJoin("PermissionGroup", "(ACL.ixPermissionGroup = PermissionGroup.ixPermissionGroup) AND (PermissionGroup.fDeleted = 0) AND (PermissionGroup.fCommunity = 1)");
			aclQuery.AddLeftJoin("PermissionGroupMember", "(PermissionGroupMember.ixPermissionGroup = PermissionGroup.ixPermissionGroup)");
			aclQuery.AddLeftJoin("EmailWildcard", "(EmailWildcard.ixPermissionGroup = PermissionGroup.ixPermissionGroup)");
			aclQuery.AddWhere("(ACL.iPermission <= 3)");
			aclQuery.AddWhere("(ACL.ixPerson = @ixPerson) OR (ACL.ixPermissionGroup = -5) OR (PermissionGroupMember.ixPerson = @ixPerson) OR (EmailWildcard.sEmailDomain = @sEmailDomain)");
			aclQuery.AddGroupBy("ACL.ixProject");
			CPerson person = api.Person.GetCurrentPerson();
			aclQuery.SetParamInt("ixPerson", person.ixPerson);
			Match match = Regex.Match(person.sEmail ?? string.Empty, @"(?<=.@).*$");
			aclQuery.SetParamString("sEmailDomain", match.Success ? match.Value.ToLowerInvariant() : "#");
			return aclQuery;
		}

		private CProjectQuery GetUserPublicProjects() {
			CProjectQuery projectQuery = api.Project.NewProjectQuery();
			projectQuery.IgnorePermissions = true;
			projectQuery.Limit = 0;
			projectQuery.ExcludeDeleted = true;
			projectQuery.ExcludeUnreadable = false;
			projectQuery.ExcludeUnreadableWithin = false;
			projectQuery.ExcludeUnwritable = false;
			projectQuery.ExcludeUnwritableWithin = false;
			projectQuery.Distinct = false;
			projectQuery.AddInnerJoinSelect(GetProjectAccess(), "ACL", "ACL.ixProject=Project.ixProject");
			projectQuery.AddWhere("(Project.fDeleted=0)");
			return projectQuery;
		}

		private string ListCommunityUserCases(int ixProject) {
			StringBuilder result = new StringBuilder();
			CEditableTable table = new CEditableTable("cases");
			table.sWidth = "100%";
			table.Header.AddCell("<span title=\"Category\">&#160;</span>").sWidth = "20px";
			table.Header.AddCell("Case").sWidth = "80px";
			table.Header.AddCell("Area").sWidth = "150px";
			table.Header.AddCell("Title");
			table.Header.AddCell("Version").sWidth = "150px";
			table.Header.AddCell("Priority").sWidth = "100px";
			table.Header.AddCell("Status").sWidth = "130px";
			IEnumerable<CBug> communityBugs = GetCommunityBugs(ixProject);
			ixProject = -1;
			int ixFixFor = -1;
			foreach (CBug bug in communityBugs) {
				bug.IgnorePermissions = true;
				bool differentProject = (bug.ixProject != ixProject);
				bool differentFixFor = differentProject || (bug.ixFixFor != ixFixFor);
				if (differentFixFor) {
					FlushTable(result, table);
					ixProject = bug.ixProject;
					ixFixFor = bug.ixFixFor;
					if (differentProject) {
						result.AppendFormat("<h1>Case List for {0}</h1>", GetHtmlStringOrEmpty(bug.QueryField("sProject")));
					}
					result.AppendFormat("<h2><a name=\"{0}\">Release {1}</h2>", XmlConvert.ToString(bug.ixFixFor), GetHtmlStringOrEmpty(bug.QueryField("sFixFor")));
				}
				CEditableTableRow row = new CEditableTableRow(XmlConvert.ToString(bug.ixBug));
				int publicStatus = !bug.fOpen ? 2 : bug.fResolved ? 1 : 0;
				bool waitingForCustomer = (publicStatus == 1) && (14.Equals(bug.QueryField("ixStatus")));
				string statusInfo = waitingForCustomer ? "Waiting On Customer" : statusInfoStrings[publicStatus];
				string bugLink = string.Format("default.asp?{0}&{1}={2}", HttpUtility.UrlEncode(ExtractShortTicket(bug.sTicket)), HttpUtility.UrlEncode(api.AddPluginPrefix("ixProject")), bug.ixProject);
				string cellClass = waitingForCustomer ? "communityWaiting" : "community"+statusInfo;
				row.AddCell(string.Format("<img src=\"{0}\" alt=\"{1}\" title=\"{1}\"/>", Icons.CategoryIconUrl((CategoryIconType)(int)bug.QueryField("nIconType")), GetHtmlStringOrEmpty(bug.QueryField("sCategory")))).sCssClass = cellClass;
				row.AddCell(string.Format("<a href=\"{0}\">{1}</a>", bugLink, bug.ixBug)).sCssClass = cellClass+" communityCaseId";
				row.AddCell(GetHtmlStringOrEmpty(bug.QueryField("sArea"))).sCssClass = cellClass;
				row.AddCell(string.Format("<a href=\"{0}\">{1}</a>", bugLink, HttpUtility.HtmlEncode(bug.sTitle))).sCssClass = cellClass;
				row.AddCell(HttpUtility.HtmlEncode(bug.sVersion)).sCssClass = cellClass;
				row.AddCell(GetHtmlStringOrEmpty(bug.QueryField("sPriority"))).sCssClass = cellClass;
				row.AddCell(statusInfo).sCssClass = cellClass;
				table.Body.AddRow(row);
				bug.IgnorePermissions = false;
			}
			if (ixProject < 0) {
				result.Append("<h2>You do not have any cases in this project</h2>");
			} else {
				FlushTable(result, table);
			}
			return result.ToString();
		}

		private string ListCommunityUserProjects() {
			StringBuilder result = new StringBuilder("<h1>Case List for Applications and Projects</h1>");
			CEditableTable table = new CEditableTable("projects");
			table.Header.AddCell("Application or Project").sWidth = "450px";
			table.Header.AddCell("Primary Contact");
			CProjectQuery projectQuery = GetUserPublicProjects();
			projectQuery.AddLeftJoin("Person", "Project.ixPersonOwner=PrimaryContact.ixPerson", "PrimaryContact");
			projectQuery.AddSelect("PrimaryContact.sFullName AS sPrimaryContactName");
			projectQuery.AddSelect("PrimaryContact.sPhone AS sPrimaryContactPhone");
			projectQuery.AddOrderBy("Project.sProject");
			result.Append("<p>");
			foreach (CProject project in projectQuery.List()) {
				project.IgnorePermissions = true;
				CEditableTableRow row = new CEditableTableRow(XmlConvert.ToString(project.ixProject));
				row.AddCell(string.Format("<a href=\"{0}\">{1}</a>", PluginPageLinkBuilder.ForPage(api).Append("ixProject", XmlConvert.ToString(project.ixProject)), HttpUtility.HtmlEncode(project.sProject)));
				StringBuilder primaryContact = new StringBuilder();
				primaryContact.Append(GetHtmlStringOrEmpty(project.QueryField("sPrimaryContactName")));
				if (primaryContact.Length > 0) {
					string phone = GetHtmlStringOrEmpty(project.QueryField("sPrimaryContactPhone"));
					if (!string.IsNullOrEmpty(phone)) {
						primaryContact.AppendFormat(" (");
						primaryContact.Append(phone);
						primaryContact.AppendFormat(")");
					}
				}
				row.AddCell(primaryContact.ToString());
				table.Body.AddRow(row);
				project.IgnorePermissions = false;
			}
			result.Append(table.RenderHtml());
			result.Append("<br/></p>");
			result.AppendFormat(
					@"
<form method=""POST"" action=""default.asp"">
	<input type=""hidden"" name=""pg"" value=""pgPublicView"" />
	<h1>{0} ({1})</h1>
	<table cellspacing=""0"">
		<tbody>
			<tr>
				<th class=""dlg"" valign=""top"">{1}:</th>
				<td class=""dlg"" ><input type=""text"" class=""dlg"" name=""sTicket"" /><div class=""editInstructions"" style=""width:300px;"">{2}</div>&nbsp;</td>
			</tr>
			<tr class=""dlg"">
				<th class=""dlg"">&nbsp;</th>
				<td class=""dlg"">
					<input type=""submit"" name=""OK"" value=""{3}"" id=""Button_OK"" class=""dlgButton"" style=""margin-top:5px;"" />
				</td>
			</tr>
		</tbody>
	</table>
</form>",
					HttpUtility.HtmlEncode(CLocalizer.Current.FB_TICKET_TITLE), HttpUtility.HtmlEncode(CLocalizer.Current.FB_TICKET), HttpUtility.HtmlEncode(CLocalizer.Current.FB_TICKET_HELP), HttpUtility.HtmlEncode(CLocalizer.Current.FB_OK));
			return result.ToString();
		}

		public string PageDisplay() {
			int ixProject;
			if (int.TryParse(api.Request[api.AddPluginPrefix("ixProject")], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out ixProject)) {
				return ListCommunityUserCases(ixProject);
			}
			return ListCommunityUserProjects();
		}

		public PermissionLevel PageVisibility() {
			return PermissionLevel.Community;
		}
	}
}
